package com.dbflow5.query.property;

import com.dbflow5.config.FlowManager;
import com.dbflow5.query.BaseModelQueriable;
import com.dbflow5.query.IConditional;
import com.dbflow5.query.IOperator;
import com.dbflow5.query.NameAlias;
import com.dbflow5.query.Operator;
import com.dbflow5.query.OrderBy;

import java.util.Collection;

/**
 * Description: The main, immutable property class that gets generated from a table definition.
 *
 *
 * This class delegates all of its [IOperator] methods to a new [Operator] that's used
 * in the SQLite query language.
 *
 *
 * This ensures that the language is strictly type-safe and only declared
 * columns get used. Also any calls on the methods return a new [Property].
 *
 *
 * This is type parametrized so that all values passed to this class remain properly typed.
 */
public class Property<T> implements IProperty<Property<T>>, IOperator<T> {

    Class<?> table;
    private final NameAlias nameAlias;

    public Property(Class<?> table, NameAlias nameAlias) {
        this.table = table;
        this.nameAlias = nameAlias;
    }

    @Override
    public Class<?> table() {
        return table;
    }

    @Override
    public NameAlias nameAlias() {
        return nameAlias;
    }

    public String definition() {
        return nameAlias.fullQuery();
    }

    /**
     * helper method to construct it in a [.distinct] call.
     * @return helper method to construct it in a [.distinct] call.
     */
    private NameAlias distinctAliasName() {
        return nameAlias
                .newBuilder()
                .distinct()
                .build();
    }

    protected Operator<T> operator() {
        return Operator.op(nameAlias());
    }

    public Property(Class<?> table, String columnName) {
        this(table, new NameAlias.Builder(columnName).build());
    }

    public Property(Class<?> table, String columnName, String aliasName) {
        this(table, NameAlias.builder(columnName).as(aliasName).build());
    }

    @Override
    public Property<T> withTable() {
        return new Property<>(table, nameAlias()
                .newBuilder()
                .withTable(FlowManager.getTableName(table))
                .build());
    }

    @Override
    public String getQuery(){
        return nameAlias().getQuery();
    }

    @Override
    public String toString() {
        return nameAlias().toString();
    }

    @Override
    public Operator<?> is(IConditional conditional) {
        return operator().is(conditional);
    }

    @Override
    public Operator<?> eq(IConditional conditional) {
        return operator().eq(conditional);
    }

    @Override
    public Operator<?> isNot(IConditional conditional) {
        return operator().isNot(conditional);
    }

    @Override
    public Operator<?> notEq(IConditional conditional) {
        return operator().notEq(conditional);
    }

    @Override
    public Operator<?> like(IConditional conditional) {
        return operator().like(conditional);
    }

    @Override
    public Operator<?> glob(IConditional conditional) {
        return operator().glob(conditional);
    }

    @Override
    public Operator<T> like(String value) {
        return operator().like(value);
    }

    @Override
    public Operator<?> match(String value) {
        return operator().match(value);
    }

    @Override
    public Operator<T> notLike(String value) {
        return operator().notLike(value);
    }

    @Override
    public Operator<T> glob(String value) {
        return operator().glob(value);
    }

    @Override
    public Operator<?> greaterThan(IConditional conditional) {
        return operator().greaterThan(conditional);
    }

    @Override
    public Operator<?> greaterThanOrEq(IConditional conditional) {
        return operator().greaterThanOrEq(conditional);
    }

    @Override
    public Operator<?> lessThan(IConditional conditional) {
        return operator().lessThan(conditional);
    }

    @Override
    public Operator<?> lessThanOrEq(IConditional conditional) {
        return operator().lessThanOrEq(conditional);
    }

    @Override
    public Operator.Between<?> between(IConditional conditional){
        return operator().between(conditional);
    }

    @Override
    public Operator.In<?> in(IConditional firstConditional, IConditional... conditionals){
        return operator().in(firstConditional, conditionals);
    }

    @Override
    public Operator.In<?> notIn(IConditional firstConditional, IConditional... conditionals){
        return operator().notIn(firstConditional, conditionals);
    }

    @Override
    public Operator<?> is(BaseModelQueriable<?> baseModelQueriable){
        return operator().is(baseModelQueriable);
    }

    @Override
    public Operator<?> isNull() { 
        return operator().isNull();
    }

    @Override
    public Operator<?> eq(BaseModelQueriable<?> baseModelQueriable){
        return operator().eq(baseModelQueriable);
    }

    @Override
    public Operator<?> isNot(BaseModelQueriable<?> baseModelQueriable){
        return operator().isNot(baseModelQueriable);
    }

    @Override
    public Operator<?> isNotNull() { 
        return operator().isNotNull();
    }

    @Override
    public Operator<?> notEq(BaseModelQueriable<?> baseModelQueriable){
        return operator().notEq(baseModelQueriable);
    }

    @Override
    public Operator<?> like(BaseModelQueriable<?> baseModelQueriable){
        return operator().like(baseModelQueriable);
    }

    @Override
    public Operator<?> notLike(IConditional conditional) { 
        return operator().notLike(conditional);
    }

    @Override
    public Operator<?> notLike(BaseModelQueriable<?> baseModelQueriable) {
        return operator().notLike(baseModelQueriable);
    }

    @Override
    public Operator<?> glob(BaseModelQueriable<?> baseModelQueriable) {
        return operator().glob(baseModelQueriable);
    }

    @Override
    public Operator<?> greaterThan(BaseModelQueriable<?> baseModelQueriable){
        return operator().greaterThan(baseModelQueriable);
    }

    @Override
    public Operator<?> greaterThanOrEq(BaseModelQueriable<?> baseModelQueriable) {
        return operator().greaterThanOrEq(baseModelQueriable);
    }

    @Override
    public Operator<?> lessThan(BaseModelQueriable<?> baseModelQueriable){
        return operator().lessThan(baseModelQueriable);
    }

    @Override
    public Operator<?> lessThanOrEq(BaseModelQueriable<?> baseModelQueriable){
        return operator().lessThanOrEq(baseModelQueriable);
    }

    @Override
    public Operator.Between<?> between(BaseModelQueriable<?> baseModelQueriable){
        return operator().between(baseModelQueriable);
    }

    @Override
    public Operator.In<?> in(BaseModelQueriable<?> firstBaseModelQueriable, BaseModelQueriable<?>... baseModelQueriables){
        return operator().in(firstBaseModelQueriable, baseModelQueriables);
    }

    @Override
    public Operator.In<?> notIn(BaseModelQueriable<?> firstBaseModelQueriable, BaseModelQueriable<?>... baseModelQueriables){
        return operator().notIn(firstBaseModelQueriable, baseModelQueriables);
    }

    @Override
    public Operator<?> concatenate(IConditional conditional){
        return operator().concatenate(conditional);
    }

    @Override
    public Operator<?> plus(BaseModelQueriable<?> value) { 
        return operator().plus(value);
    }

    @Override
    public Operator<?> minus(BaseModelQueriable<?> value) { 
        return operator().minus(value);
    }

    @Override
    public Operator<?> div(BaseModelQueriable<?> value) { 
        return operator().div(value);
    }

    @Override
    public Operator<?> times(BaseModelQueriable<?> value) { 
        return operator().times(value);
    }

    @Override
    public Operator<?> rem(BaseModelQueriable<?> value) { 
        return operator().rem(value);
    }

    @Override
    public Property<T> plus(IProperty<?> property) {
        return new Property<>(table, NameAlias.joinNames(Operator.Operation.PLUS,
            nameAlias.fullName(), property.toString()));
    }

    @Override
    public Property<T> minus(IProperty<?> property) {
        return new Property<>(table, NameAlias.joinNames(Operator.Operation.MINUS,
            nameAlias.fullName(), property.toString()));
    }

    @Override
    public Property<T> div(IProperty<?> property) {
        return new Property<>(table, NameAlias.joinNames(Operator.Operation.DIVISION,
            nameAlias.fullName(), property.toString()));
    }

    @Override
    public Property<T> times(IProperty<?> property) {
        return new Property<>(table, NameAlias.joinNames(Operator.Operation.MULTIPLY,
            nameAlias.fullName(), property.toString()));
    }

    @Override
    public Property<T> rem(IProperty<?> property) {
        return new Property<>(table, NameAlias.joinNames(Operator.Operation.MOD,
            nameAlias.fullName(), property.toString()));
    }

    @Override
    public Property<T> concatenate(IProperty<?> property) {
        return new Property(table, NameAlias.joinNames(Operator.Operation.CONCATENATE,
            nameAlias.fullName(), property.toString()));
    }

    @Override
    public Property<T> as(String aliasName) {
        return new Property<>(table, nameAlias
            .newBuilder()
            .as(aliasName)
            .build());
    }

    @Override
    public Property<T> distinct() {
        return new Property<>(table, distinctAliasName());
    }

    @Override
    public Property<T> withTable(NameAlias tableNameAlias) {
        return new Property<>(table, nameAlias
            .newBuilder()
            .withTable(tableNameAlias.tableName)
            .build());
    }

    @Override
    public Operator<T> is(T value) {
        return operator().is(value);
    }

    @Override
    public Operator<T> eq(T value) {
        return operator().eq(value);
    }

    @Override
    public Operator<T> isNot(T value) {
        return operator().isNot(value);
    }

    @Override
    public Operator<T> notEq(T value) {
        return operator().notEq(value);
    }

    @Override
    public Operator<T> greaterThan(T value) {
        return operator().greaterThan(value);
    }

    @Override
    public Operator<T> greaterThanOrEq(T value) {
        return operator().greaterThanOrEq(value);
    }

    @Override
    public Operator<T> lessThan(T value) {
        return operator().lessThan(value);
    }

    @Override
    public Operator<T> lessThanOrEq(T value) {
        return operator().lessThanOrEq(value);
    }

    @Override
    public Operator.Between<T> between(T value) {
        return operator().between(value);
    }

    @Override
    public Operator.In<T> in(T firstValue, T... values){
        return operator().in(firstValue, values);
    }

    @Override
    public Operator.In<T> notIn(T firstValue, T... values){
        return operator().notIn(firstValue, values);
    }

    @Override
    public Operator.In<T> in(Collection<T> values){
        return operator().in(values);
    }

    @Override
    public Operator.In<T> notIn(Collection<T> values){
        return operator().notIn(values);
    }

    @Override
    public Operator<T> concatenate(Object value){
        return operator().concatenate(value);
    }

    @Override
    public Operator<T> plus(T value){
        return operator().plus(value);
    }

    @Override
    public Operator<T> minus(T value){
        return operator().minus(value);
    }

    @Override
    public Operator<T> div(T value){
        return operator().div(value);
    }

    @Override
    public Operator<T> times(T value){
        return operator().times(value);
    }

    @Override
    public Operator<T> rem(T value){
        return operator().rem(value);}

    @Override
    public OrderBy asc(){
        return OrderBy.fromProperty(this, true).ascending();
    }

    @Override
    public OrderBy desc(){
        return OrderBy.fromProperty(this, true).descending();
    }

    public static final Property<String> ALL_PROPERTY = new Property<>(null, com.dbflow5.query.NameAlias.rawBuilder("*").build());

    public static final Property<String> NO_PROPERTY = new Property<String>(null, NameAlias.rawBuilder("").build());

    public static final Property<Object> WILDCARD = new Property<>(null, NameAlias.rawBuilder("?").build());

    public static Property<String> allProperty(Class<?> table) {
        return new Property<String>(table, NameAlias.rawBuilder("*").build()).withTable();
    }
}


